# -*- coding:utf-8 -*-

import os, io
import copy
from collections import OrderedDict
import requests
import json
import itertools
from metlib.misc.download import *
from metlib.shell.fileutil import *

__all__ = ["LookupError",
           "GetDataError", "NoData", "BadDataParameter", "DataNoAccess",
           "GetFileError",
           "DatasetMoldBase", "ForwardDatasetMold", "_dataset_cache"]


class LookupError(Exception):
    """查询异常"""
    pass


class GetDataError(Exception):
    """获取数据异常"""
    pass


class NoData(GetDataError):
    """当数据集中没有所需数据时抛出的异常."""
    pass


class BadDataParameter(GetDataError):
    """获取数据参数异常"""
    pass


class DataNoAccess(GetDataError):
    """用户没有访问权限"""
    pass


class GetFileError(Exception):
    """获取文件异常"""
    pass


class DatasetMoldBase(object):
    """数据集模板的基类.

    """
    mold_name = ''   # 模板名
    subset_classes = []  # list of dicts like {'name':, 'uri':, 'class':}
    info_template = "{}"  # 用来实例化数据集对象时所用的info字典的JSON化范本.
                          # 之所以用字符串, 是因为可保持可读的格式.
    uri_fields = ['dataset', 'args']  # uri格式规范
    uri_field_values = {}  # uri各部分的可选值列表
    tags = []           #标签, 供筛选数据集使用

    @classmethod
    def get_mold_desc(cls):
        return ''

    @classmethod
    def import_dataset(cls, src, dest, meta=None, *args, **kwargs):
        try:
            if meta is None:
                meta_fname = os.path.join(src, 'meta.json')
                if os.path.exists(meta_fname):
                    meta = json.load(io.open(meta_fname, encoding='utf-8'), strict=False)
                else:
                    meta = {
                        "dataset": BASENAME(dest.rstrip('/')),
                        "type": cls.mold_name,
                        "creator": "Unknown",
                        "version": "Unknown",
                        "patch": False,
                        "desc": "",
                        "info": {}
                    }
            patch = meta.get('patch', False)
            if not patch:
                RM(dest)
            CP(src, dest)
            meta['size'] = DU(dest)
            return meta
        except Exception as e:
            return unicode(e)

    def __init__(self, name, uri, info, *args, **kwargs):
        self.name = name
        self.uri = uri
        self.info = info
        self.db_obj = kwargs.get('db_obj', None)  # 数据库层级的对象

        self._parent = None
        self._subsets = OrderedDict()
        self._uri_subsets = OrderedDict()
        for item in self.subset_classes:
            subset = item['class'](item['name'], item['uri'], info, *args, **kwargs)
            self.add_subset(subset)


    @property
    def hierarchical_name(self):
        """层级中的名字"""
        if self._parent is None:
            return self.name
        else:
            return self._parent.hierarchical_name + '/' + self.name

    @property
    def hierarchical_uri(self):
        """层级中的uri"""
        if self._parent is None:
            return self.uri
        else:
            return self._parent.hierarchical_uri + '/' + self.uri

    @property
    def parent(self):
        return self._parent
    @parent.setter
    def parent(self, new_p):
        self._parent = new_p

    @property
    def topset(self):
        """最顶层的数据集"""
        if self._parent:
            return self._parent.topset
        else:
            return self

    @property
    def subsets(self):
        """子集们(以名字为key的有序字典)"""
        return self._subsets

    @property
    def uri_subsets(self):
        """子集们(以uri为key的有序字典)"""
        return self._uri_subsets

    def add_subset(self, subset, copy=False):
        """增加子集"""
        to_append = subset.copy() if copy else subset
        to_append._parent = self
        self._subsets[subset.name] = to_append
        self._uri_subsets[subset.uri] = to_append

    def del_subset(self, subset_name=None, subset_uri=None):
        """删除子集"""
        pass  # To implement

    def copy(self):
        return copy.deepcopy(self)

    def is_public(self):
        """是否为公开的"""
        if self.topset.db_obj:
            return self.topset.db_obj.public
        else:
            return True

    def is_free(self, uri=None, *args, **kwargs):
        """是否为免费的"""
        if self.topset.db_obj:
            return self.topset.db_obj.free
        else:
            return True

    @property
    def schema(self):
        return {}

    def get_schema(self, request=None, *args, **kwargs):
        """可带参数的获取schema的方式. """
        return self.schema

    def parse_uri(self, uri):
        """将uri解析为各field的键值对."""
        tokens = uri.split('/', len(self.uri_fields)-1)
        return dict(zip(self.uri_fields, tokens))

    def unzip_uri(self, uri, sep=','):
        """将uri中的合并项解析开, 得到子uri列表"""
        uri_d = self.parse_uri(uri)
        # suburi_template = uri.replace('*', '%s')
        iter_fields = []

        suburis = []
        for field in self.uri_fields:
            part = uri_d[field]
            if part == '*':
                iter_fields.append(self.uri_field_values.get(field, ['*']))
                suburis.append('%s')
            elif sep in part:
                iter_fields.append(part.split(sep))
                suburis.append('%s')
            else:
                suburis.append(part)
        suburi_template = '/'.join(suburis)

        res = []
        for info in itertools.product(*iter_fields):
            res.append(suburi_template % info)
        return res

    def minimize_uri(self, uri, *args, **kwargs):
        """将uri转化为最简形式"""
        return uri

    def lookup(self, info, request=None, *args, **kwargs):
        """查询"""
        raise NotImplementedError

    def get_data(self, uri, request=None, *args, **kwargs):
        """获取数据

        :param uri: 数据的uri
        :param request:
        :param args:
        :param kwargs:
        :return:
        """
        raise NotImplementedError

    def get_file(self, uri, dest, request=None, *args, **kwargs):
        """获取文件

        :param uri: 数据的uri
        :param dest: 目标路径
        :param request:
        :param args:
        :param kwargs:
        :return: fileinfo dict like: {
            "status": "OK" | "BadParameter" | "NoData",
            "filetype":,
            "format": ,
            }
        """
        raise NotImplementedError



class ForwardDatasetMold(DatasetMoldBase):
    """这是一种用来将另一台服务器提供的数据集中转过来的数据集模板.
    其info中需要有以下几个参数:
    remote_host: 远端服务器的域名和端口
    remote_data_api: 远端数据集的取数据接口(不带域名部分), 如 'data' 等.
    remote_name: 远端数据集的名字, 可选, 默认为本数据集的名字.
    remote_lookup_api: 远端服务器所提供的lookup接口. 可选, 默认为 'lookup' .
    remote_lookup_dataset_type: 远端数据集的数据集模板类型, 供lookup时使用. 可选, 默认为与本地参数相同.
    """
    info_template = """{
    "remote_host": "http://localhost:8000",
    "remote_data_api": "data",
    "remote_name": "",
    "remote_lookup_api": "lookup",
    "remote_lookup_dataset_type": ""
}"""

    def __init__(self, name, uri, info, *args, **kwargs):
        super(ForwardDatasetMold, self).__init__(name, uri, info, *args, **kwargs)
        self.remote_host = info['remote_host'].rstrip('/')
        self.remote_lookup_api = info.get('remote_lookup_api', 'lookup').lstrip('/')
        self.remote_data_api = info.get('remote_data_api', 'data').rstrip('/')
        self.remote_lookup_dataset_type = info.get('remote_lookup_dataset_type', "")
        self.remote_name = info.get('remote_name', "")
        if not self.remote_name:
            self.remote_name = self.name

    def lookup(self, info, request=None, *args, **kwargs):
        try:
            kwstrs = []
            for key, value in info.iteritems():
                if key == 'dataset_type' and self.remote_lookup_dataset_type:
                    kwstrs.append('%s=%s' % (key, self.remote_lookup_dataset_type))
                else:
                    kwstrs.append('%s=%s' % (key, value))

            kwstr = '&'.join(kwstrs)
            remote_url = '?'.join(['%s/%s' % (self.remote_host, self.remote_lookup_api), kwstr])
            r = requests.get(remote_url)
            if r.status_code == 200:
                raw_results = r.json()['objects']
                trans_results = []
                for res in raw_results:
                    trans_res = res.copy()
                    trans_res['dataset'] = self.name
                    raw_uri = trans_res['uri']
                    raw_dataset, raw_rest = raw_uri.split('/', 1)
                    trans_res['uri'] = '/'.join([self.name, raw_rest])
                    trans_results.append(trans_res)
                return trans_results
            else:
                return []
        except Exception as e:
            raise LookupError(unicode(e))

    def get_file(self, uri, dest, request=None, *args, **kwargs):
        try:
            kwstrs = []
            for key, value in kwargs.iteritems():
                kwstrs.append('%s=%s' % (key, value))
            kwstr = '&'.join(kwstrs)
            uri_wo_ds = uri.split('/', 1)[1]
            remote_url = '?'.join(['%s/%s/%s/%s' % (self.remote_host, self.remote_data_api, self.remote_name, uri_wo_ds), kwstr])
            download(remote_url, dest=dest, retry=3, interval=1)
            return {'status': 'OK'}
        except Exception as e:
            raise GetFileError(unicode(e))

# 实际使用时, 为保证dataset对象的唯一性而设置的缓存.
_dataset_cache = {}